package com.livestocksolutions.intent;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.Stack;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.text.format.DateFormat;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.SearchView;
import android.widget.SearchView.OnQueryTextListener;
import android.widget.Toast;

import com.livestocksolutions.Bovine;
import com.livestocksolutions.Group;
import com.livestocksolutions.Join;
import com.livestocksolutions.LivestockSolutions;
import com.livestocksolutions.Location;
import com.livestocksolutions.MedicalHistory;
import com.livestocksolutions.Observation;
import com.livestocksolutions.Owner;
import com.livestocksolutions.R;
import com.livestocksolutions.UsableItem;
import com.livestocksolutions.UsableItemHistory;
import com.livestocksolutions.util.Comparators;

/* -- Version History --
 *  1.0 : 10 September 2012 - Initial version.
 *  
 *  1.1 : 27 October 2012 - Moved comparator operations to com.livestocksolutions.util.Comparators class
 *                          which is common between both Android and PC versions.
 */

/**
 * A child class of Activity, which contains many common methods to classes
 * that will extend this class.
 * @author Adam Parr
 * @version 1.1
 * 
 * @.modified 27 October 2012
 */
public class BaseActivity extends Activity{
	/**
	 * For when a new activity is started, but does not require any
	 * special handling.
	 */
	protected static final int NONE = 0;
	/**
	 * For when a new activity is started using an existing Object.<br>
	 * Used in the requestCode to indicate this is an existing Object.
	 */
	protected static final int EXISTING = 1;
	/**
	 * For when a new activity is started using no Object.<br>
	 * Used in the requestCode to indicate this is a new Object.
	 */
	protected static final int NEW = 2;
	/**
	 * ID given to identify the Integer value for which {@link #RESOURCEID} is the one to
	 * use for the current state.
	 */
	protected static final String STATEID = "id";
	/**
	 * The ID of the Object that is in view.
	 */
	protected static final String ITEMID = "idRef";
	/**
	 * The check to see if this is currently in edit mode.
	 */
	protected static final String EDIT = "editMode";
	
	/**
	 * A comparator for comparing Bovine animals by Visual ID.
	 */
	protected Comparator<Bovine> bovineComparator = Comparators.bovineComparatorVISID;
	
	/**
	 * A comparator for comparing Groups and Mobs.
	 */
	protected static Comparator<Group> groupComparator = Comparators.groupComparatorName;
	
	/**
	 * A comparator for comparing Observations.
	 */
	protected static Comparator<Observation> observationComparator = Comparators.observationComparatorPriority;
	
	/**
	 * A comparator for comparing Joins.
	 */
	protected static Comparator<Join> joinComparator = Comparators.joiningComparatorEntry;
	
	/**
	 * A comparator for comparing Medical Histories.
	 */
	protected static Comparator<MedicalHistory> medicalHistoryComparator = Comparators.medicalItemHistoryComparatorDate;
	
	/**
	 * A comparator for comparing UsableItem Histories.
	 */
	protected static Comparator<UsableItemHistory> usableItemHistoryComparator = Comparators.usableItemHistoryComparatorDate;
	
	/**
	 * A comparator for comparing Location and Paddocks.
	 */
	protected static Comparator<Location> locationComparator = Comparators.locationComparatorName;
	
	/**
	 * A comparator for comparing UsableItems.
	 */
	protected static Comparator<UsableItem> usableItemComparator = Comparators.usableItemComparatorName;
	
	/**
	 * A comparator for comparing Owners.
	 */
	protected static Comparator<Owner> ownerComparator = Comparators.ownerComparatorName;
	
	/**
	 * A flag in which the Current Activity should be in Edit mode (<tt>true</tt>) or not (<tt>false</tt>)
	 */
	protected boolean edit = false;
	/**
	 * A flag in which the Current Activity is empty (<tt>true</tt>) or not (<tt>false</tt>)
	 */
	protected boolean empty=false;
	/**
	 * A flag in which the Current Activity has already been made (<tt>true</tt>) or not (<tt>false</tt>)
	 */
	protected boolean built = false;
	
	/**
	 * Store the value for the Menu Object so that it can be changed when needed.
	 */
	protected Menu menu;
	
	/**
	 * The ID of this Object
	 */
	protected long id = -1;
	
	/**
	 * The time in miliseconds at which the back button was pressed in an attempt to go back.
	 */
	protected long lastBack = 0;
	
	/**
	 * The time in miliseconds at which pushing the back button again will result in a
	 * super call to return the application to the previous Activity.
	 */
	protected long backWait = 3000;
	
	/**
	 * Stores the reference to the XML layout template for each view
	 */
	public final Integer[] RESOURCEID;
	/**
	 * Keeps an instance of Activity to allow access to be able to get resources
	 */
	protected static Activity activity;
	/**
	 * History of movements that have occurred in this activity
	 */
	protected Stack<Bundle> stack = new Stack<Bundle>();
	/**
	 * The current state of this activity
	 */
	protected Bundle currentState;
	/**
	 * The current selected view
	 */
	protected View selected;
	
	/**
	 * The parent most Activity to add any data to return back to the calling Activity.<br>
	 * When {@link #finish()} is called the intent of this Activity is added to the Activity
	 * in mParent
	 */
	protected Activity mParent;
	
	/**
	 * Constructor
	 * @param resourceIds the Integer array to set {@link #RESOURCEID} to
	 */
	protected BaseActivity(Integer[] resourceIds){
		super();
		// Assign the activity if not yet assigned.
		if(activity == null){
			activity = this;
		}
		RESOURCEID = resourceIds;
		
		mParent = getParent() == null ? this : getParent();
	}
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		create(savedInstanceState);
    }
	
	/**
	 * @see #onCreate(Bundle)
	 * @param savedInstanceState
	 */
	public final void create(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		
        // Create a new Bundle to represent this current State if one does not already exist
        currentState = savedInstanceState==null? new Bundle() : savedInstanceState;
        stack = stack==null?new Stack<Bundle>():stack;
        
        pullStack(currentState);
        
        handleIntent(getIntent());
	}
	
	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		if(menu!=null){
		    MenuItem edit = menu.findItem(R.id.menu_edit);
		    MenuItem save = menu.findItem(R.id.menu_save);
		    MenuItem add = menu.findItem(R.id.add_menu);
		    if(add!=null){	
		    	add.setVisible(true);
		    }
		    if(this.edit) {
		    	if(edit!=null){
		    		edit.setVisible(false);
		    	}
		    	if(save!=null){
		    		save.setVisible(true);
		    	}
		    	if(add!=null){	
		    		add.setVisible(false);
		    	}
		    }else if(!empty){
		    	if(edit!=null){
		    		edit.setVisible(true);
		    	}
		    	if(save!=null){
		    		save.setVisible(false);
		    	}
		    	if(add!=null){	
		    		add.setVisible(true);
		    	}
		    }else{
		    	if(edit!=null){
		    		edit.setVisible(false);
		    	}
		    	if(save!=null){
		    		save.setVisible(false);
		    	}
		    	if(add!=null){	
		    		add.setVisible(false);
		    	}
		    }
		    return true;
		}
		return false;
	}
	 
	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	@Override
    public boolean onCreateOptionsMenu(Menu menu) {
    	// Show a default menu
        getMenuInflater().inflate(R.menu.main_menu, menu);
        if(android.os.Build.VERSION_CODES.HONEYCOMB<=android.os.Build.VERSION.SDK_INT){
	        SearchView searchView = (SearchView)menu.findItem(R.id.menu_search).getActionView();
	        searchView.setOnQueryTextListener(new OnQueryTextListener(){
				public boolean onQueryTextSubmit(String query) {
					Intent intent = new Intent(BaseActivity.this, SearchActivity.class);
					intent.putExtra(SearchManager.QUERY, query);
					intent.setAction(Intent.ACTION_SEARCH);
					startActivityForResult(intent, NONE);
					return false;
				}
				public boolean onQueryTextChange(String newText) {return false;}
	        });
        }
        this.menu=menu;
        return true;
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    	return frameSelection(item.getItemId())?true:super.onOptionsItemSelected(item);
    }
    
    @Override
    public void finish(){
    	mParent.setIntent(getIntent());
    	if(this!=mParent){
    		mParent.finish();
    	}else{
    		super.finish();
    	}
    }
	
    @Override
    public void onBackPressed() {
    	setSelected(null);
    	
    	if(edit){
	    	edit=false;
	    	built=false;
	    	Stack<Bundle> bStack = new Stack<Bundle>();
	    	for(Bundle b : stack){
	    		bStack.push(b);
	    	}
	    	stack.clear();
	    	onCreate(currentState);
	    	for(Bundle b : bStack){
	    		stack.push(b);
	    	}
	    	onPrepareOptionsMenu(menu);
    	}
    	
		// Get stored data from the previous from, which will be shown now
		if(stack.size()>0){
    		Bundle state = stack.peek();
    		showView(state.getInt(STATEID),state);
    		getScrollPosition(state);
    		currentState=state;
		}else{
			// No previous frame, allow System to determine what the back button does now
			if(this instanceof HomeActivity){
				if(lastBack + backWait > new Date().getTime()){
				super.onBackPressed();
				}else{
					Toast.makeText(this,getResources().getString(R.string.message_pushBackToExit,getResources().getString(R.string.message_close)), Toast.LENGTH_SHORT).show();
					lastBack = new Date().getTime();
				}
			}else{
				super.onBackPressed();
			}
		}
    }
    
    @Override
    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && !stack.isEmpty()) {
        	setSelected(null);
            Bundle state = stack.firstElement();
    		showView(state.getInt(STATEID),state);
    		getScrollPosition(state);
    		stack.removeAllElements();
    		currentState=state;
            return true;
        }
        return super.onKeyLongPress(keyCode, event);
    }

    
    @Override
    protected void onSaveInstanceState (Bundle outState) {
    	saveInstanceState(outState);
    }
    
    /**
     * Direct call to onSaveInstanceState in the BaseActivity and any super clases
     * @param outState Bundle in which to place your saved state
     */
    public final void saveInstanceState (Bundle outState) {
    	super.onSaveInstanceState (outState);
    	outState.putInt(STATEID, currentState.getInt(STATEID));
    	
    	outState.putBoolean("empty",empty);
    	outState.putBoolean("built", built);
    	outState.putBoolean(EDIT, edit);
    	outState.putLong(ITEMID, id);
    	
    	putStack(outState);
    	
    	setScrollPosition(outState);
    }
    
    @Override
    protected void onRestoreInstanceState(Bundle inState) {
    	restoreInstanceState(inState);
    }
    
    /**
     *  Direct call to onSaveInstanceState in the BaseActivity and any super clases
     * @param inState The data most recently supplied in {@link #onSaveInstanceState(Bundle)}
     */
    public final void restoreInstanceState(Bundle inState) {    	
    	empty=inState.getBoolean("empty");
    	built=inState.getBoolean("built");
    	edit=inState.getBoolean(EDIT);
    	id=inState.getLong(ITEMID);
    	
    	getScrollPosition(inState);
    	
    	if(stack.size()>0){
    		// Show the top most current frame
    		showView(currentState.getInt(STATEID),stack.pop());
    	}
    	try{
    		super.onRestoreInstanceState (inState);
    	}catch(Exception e){
    		e.printStackTrace();
    	}
    }
    
    @Override
    protected void onNewIntent(Intent intent) {
        setIntent(intent);
        handleIntent(intent);
    }
    
    /**
     * Adds to the Bundle the current position of the ScrollView ui with the ID given by R.id.scrollView
     * @param state Bundle to save the ScrollView position to
     */
    protected void setScrollPosition(Bundle state) {
    	View view;
    	if((view = findViewById(R.id.scrollView))!=null){
	    	state.putIntArray("ARTICLE_SCROLL_POSITION", new int[]{view.getScrollX(), view.getScrollY()});
    	}
    }
    
    /**
     * Sets the scroll position of the ScrollView ui with the ID given by R.id.scrollView
     * @param state to get the ScrollView position from
     */
    protected void getScrollPosition(Bundle state) {
    	final int[] position = state.getIntArray("ARTICLE_SCROLL_POSITION");
    	final View view;
	    if(position != null && (view = findViewById(R.id.scrollView)) != null) {
	    	view.post(new Runnable() {
	            public void run() {
	            	view.scrollTo(position[0], position[1]);
	            }
	        });
	    }
    }
    
    @Override
    public void onPause(){
    	pause();
    }
    
    /**
     * @see #onPause()
     */
    protected final void pause(){
    	super.onPause();
    }
    
    @Override
    public void onResume(){
    	resume();
    }
    
    /**
     * @see #onResume()
     */
    protected final void resume(){
    	super.onResume();
    	
    	Bundle state = currentState;
		showView(currentState.getInt(STATEID),currentState);
		getScrollPosition(currentState);
		stack.pop();
		currentState=state;
		
		// Check to see if the data file has been updated...
		LivestockSolutions.checkUpdatedData();
    }
    
    /**
     * Changes the main view (frame) to the frame with the id passed to this method
     * @param frame an int id that identifies the frame to switch to
     * @return <code>true</code> if the frame identified by the integer given by the attribute <code>frame</code>
     * is one of the supported frames, <code>false</code> otherwise
     */
    protected boolean frameSelection(int frame) {
    	 switch (frame) {
    	 	case R.id.menu_search:
    	 		
    	 		break;
    	 	case R.id.menu_edit:
	     		edit = true;
	     		onPrepareOptionsMenu(menu);
	     		showView(currentState.getInt(STATEID),currentState);
	     		break;
     		case R.id.menu_settings:
     			Intent intent = new Intent(BaseActivity.this,SettingsActivity.class);
     			startActivity(intent);
     			break;
	        default:
	            return false;
    	 }
    	 return true;
    }
    
    /**
     * Creates the contents of the page and displays it
     * @param view the id of the view to create the contents of the page for
     * @return <code>true</code> if the given id is valid layout id
     */
    protected boolean constructFrame(int view) {
    	return false;
    }
    
    /**
     * Checks to see if the stack has been stored, load this stored stack if it exists.
     * @param state The Bundle to check and get the stack.
     */
    protected final void pullStack(Bundle state){
    	if(stack==null || stack.size()==0){
    		try{
    			Bundle bundleStack = state.getBundle("stack");
	    		
		        if(bundleStack!=null){
		        	for(int x=0; x<bundleStack.getInt("length"); x++){
		        		Bundle b = bundleStack.getBundle(String.valueOf(x));
		        		stack.push(b);
		        	}
		        }
		        state.putBundle("stack",null);
    		}catch(Exception e){
    			
    		}
    	}
    }
    
    /**
     * Saves the stack to the given Bundle.
     * @param state The Bundle to save the current state of the stack to.
     */
    protected final void putStack(Bundle state){
    	int size = stack.size();
        Bundle bundleStack = new Bundle();
        for(int x=0; x<size; x++){
    		bundleStack.putBundle(String.valueOf(x), stack.get(x));
    	}
		bundleStack.putInt("length", size);
		state.putBundle("stack", bundleStack);
    }
    
	/**
     * Show the view that has the given int id
     * @param view The id of the view to show
     * @param state The current state of the view
     */
    protected void showView(int view, Bundle state) {
    	if(stack.empty() || !state.equals(stack.peek())){
    		setScrollPosition(state);
	    	// Adds the current view to the top of the page;
    		stack.push(state);
    	}else{
    		// Remove this current view from the stack
    		stack.pop();
    	}
    	// Change the view
    	setContentView(RESOURCEID[view]);
    	constructFrame(view);
	    currentState=new Bundle();
	    currentState.putInt(STATEID, view);
    }
    
	/**
	 * Inflate a View from the given layout resource ID.
	 * @param layoutId The Resource ID of the layout to inflate.
	 * @param parent The Parent where this inflated View will be inserted into.
	 * @return The View that was just inflated.
	 */
	protected View inflateLayout(int layoutId, ViewGroup parent){
		return ((LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(layoutId, parent, false);
	}
	
	/**
	 * Setup a Title view.
	 * @param viewId The View where the Title View will be inserted into.
	 * @param layoutId The Resource ID of the View to inflate.
	 */
	protected void setupListTitle(int viewId, int layoutId){
		LinearLayout listView = (LinearLayout) findViewById(viewId);
		if(listView != null){
	    	listView.removeAllViews();
	    	listView.addView(inflateLayout(layoutId,listView));
	    	listView.findViewById(R.id.title).setVisibility(View.VISIBLE);
	    	listView.setBackgroundResource(R.drawable.rounded_dark);
		}
	}
	
	/**
	 * Get the loading screen
	 * @param parent The parent that the loading screen will be attached to.
	 * @return The loading screen.
	 */
	protected View getLoading(ViewGroup parent){
		return inflateLayout(R.layout.loading,parent);
	}
    
    /**
     * Get an instance of Activity
     * @return an instance of Activity
     */
    public static Activity getActivity(){
    	return activity;
    }
    
    /**
     * Get the current state Bundle
     * @return The current State of this Activity
     */
    public Bundle getCurrentState(){
    	return currentState;
    }
    
    /**
     * Get the View that has been set as selected via {@link #setSelected(View)}.
     * @return The currently "Selected" View.
     */
    public View getSelected(){
    	return selected;
    }
    
    /**
     * Set a View to be currently selected
     * @param view View to remember as being selected.
     */
    public void setSelected(View view){
    	selected = view;
    }
    
    /**
     * Handle intents based on the intent given
     * @param intent Intent to be handled.
     */
    protected void handleIntent(Intent intent) {
    	if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
    		doSearch(intent.getStringExtra(SearchManager.QUERY));
    	}
	}
    
    /**
     * Do a search for any matching strings given in <code>search</code>.<br>
     * By default does nothing, override to use.
     * @param search String containing the Search Query
     */
    protected void doSearch(String search){
    	currentState.putString(SearchManager.QUERY, search);
    }
    
    /**
     * Convert a Date into a human readable String in the locale format.
     * @param date The Date to format.
     * @return String representing the Date in a human readable format.
     */
    public String format(Date date){
    	return date!=null?DateFormat.getDateFormat(this).format(date):"";
    }
    
    /**
     * Convert a String into a Date.
     * @param dateString The String representation of the Date to be formatted.
     * @return The Date or null if the String is not of a format provided in
     *          the Locale expected.
     */
    public Date parse(String dateString){
    	dateString=dateString.replaceAll(",", "/").replaceAll("\\.", "/").replaceAll("-", "/").trim().replaceAll(" ", "/");
    	if(dateString.contains("/")||dateString.equals("")){
    		if(dateString.split("/").length==3) {
		    	try {
		    		
					Date d = DateFormat.getDateFormat(this).parse(dateString);
					Calendar c =Calendar.getInstance();
					c.setTime(d);
					if(c.get(Calendar.YEAR)<1970){
						c.add(Calendar.YEAR, 2000);
					}else if(c.get(Calendar.YEAR)<1900){
						c.add(Calendar.YEAR, 1900);
					}
					return c.getTime();
		    	} catch (ParseException e) {
					e.printStackTrace();
				}
    		}
    		return null;
    	}
		return new Date(Long.parseLong(dateString));
    }
    
    /**
     * Get the String representation of the Date in the localised format.
     * @return String of the format of the Date.
     */
    public String getDateFormat() {
    	final int DAY = 31;
    	final int MONTH = Calendar.DECEMBER;
    	final int YEAR = 2060;
    	final int YEAR2 = 60;
    	Calendar cal = Calendar.getInstance();
    	cal.set(Calendar.YEAR,YEAR);
    	cal.set(Calendar.MONTH, MONTH);
    	cal.set(Calendar.DATE, DAY);
    	
    	String s = format(cal.getTime());
    	String[] sa = s.split("/");
    	
    	String format = "";
    	
    	for(int x = 0; x<sa.length;x++) {
    		int iv = Integer.parseInt(sa[x]);
    		switch(iv) {
    		case DAY:
    			format+="dd/";
    			break;
    		case MONTH:
    			format+="MM/";
    			break;
    		case YEAR:
    			format+="yyyy/";
    			break;
    		case YEAR2:
    			format+="yy/";
    			break;
    		}
    	}
    	return format.substring(0,format.length()-1);
    }
    
    /**
     * Get a long format of the date, involving appending the day as a String to the front,
     * and changing the month to the string representation, an example would be if the date
     * in the short form is "dd/MM/yyyy", then the long format would be "EEEE, dd MMMM, yyyy"
     * @return String representing the format a date should be formatted to in long format.
     * 
     * @see #getDateFormat()
     */
    public String getDateFormatLong() {
    	return "EEEE, "+getDateFormat().replaceAll("MM", "MMMM").replaceAll("/y", ", y").replaceAll("/", " ");
    }
}